/*
JuffEd - A simple text editor
Copyright 2007 Murzin Mikhail

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License 
version 2 as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include "DocHandler.h"

//	local
#include "CommandStorage.h"
#include "FindDlg.h"
#include "MainSettings.h"
#include "NullTextDoc.h"
#include "TextDoc.h"
#include "TextDocWidget.h"

//	Qt
#include <QtGui/QAction>
#include <QtGui/QFileDialog>
#include <QtGui/QInputDialog>
#include <QtGui/QMainWindow>
#include <QtGui/QMessageBox>
#include <QtGui/QTabBar>
#include <QtGui/QTabWidget>

class DocStorage {
public:
	DocStorage() { }
	~DocStorage() {
		while (docs_.count() > 0) {
			delete docs_.at(0);
			docs_.remove(0);
		}
	}

	void addDoc(TextDoc* doc) {
		if (doc != 0 && !doc->isNull())
			docs_.append(doc);
	}

	TextDoc* getDoc(int index) {
		if (index >= 0 && index < docs_.count())
			return docs_.at(index);
		else
			return NullTextDoc::instance();
	}

	void removeDoc(int index) {
		TextDoc* doc = getDoc(index);
		if (!doc->isNull()) {
			docs_.remove(index);
			delete doc;
		}
	}

	int findIndex(const QString& fileName) {
		int index = -1;
		int i = 0;
		foreach (TextDoc* doc, docs_) {
			if (doc->fileName().compare(fileName) == 0) {
				index = i;
				break;
			}
			i++;
		}
		return index;
	}

	TextDoc* findTextDoc(const QString& fileName) {
		TextDoc* d = 0;
		foreach (TextDoc* doc, docs_) {
			if (doc->fileName().compare(fileName) == 0) {
				d = doc;
				break;
			}
		}
		return d == 0 ? NullTextDoc::instance() : d;
	}

	void applySettings() {
		foreach (Document* doc, docs_)
			doc->applySettings();
	}

private:
	QVector<TextDoc*> docs_;
};

TextDoc* getTextDoc(Document* doc) {
	TextDoc* td = qobject_cast<TextDoc*>(doc);
	return td == 0 ? NullTextDoc::instance() : td;
}

TextDoc* getTextDoc(QWidget* w) {
	TextDocWidget* dw = qobject_cast<TextDocWidget*>(w);
	return dw == 0 ? NullTextDoc::instance() : getTextDoc(dw->document());
}

///////////////////////////////////////////////////////////////////////

class TabWidget : public QTabWidget {
public:
	TabWidget() : QTabWidget() { tabBar()->setFocusPolicy(Qt::NoFocus); }
};

DocHandler::DocHandler(QMainWindow* mw) : QObject(), mw_(mw) {
	tabWidget_ = new TabWidget();
	mw_->setCentralWidget(tabWidget_);
	connect(tabWidget_, SIGNAL(currentChanged(int)), SLOT(changeCurDoc(int)));
	storage_ = new DocStorage();
}

DocHandler::~DocHandler() {
	delete storage_;
	delete tabWidget_;
}

void DocHandler::applySettings() {
	tabWidget_->setTabPosition((QTabWidget::TabPosition)MainSettings::tabPosition());
	storage_->applySettings();
}

Document* DocHandler::createDoc(const QString& name) {
	TextDoc* doc = new TextDoc(name, tabWidget_);
	connect(doc, SIGNAL(modified(bool)), SLOT(docModified(bool)));
	connect(doc, SIGNAL(cursorPosChanged()), SLOT(showCursorPos()));
	return doc;
}


///////////////////////////////////////////////////////////////////////
//	TabWidget related methods
///////////////////////////////////////////////////////////////////////

Document* DocHandler::currentDoc() const {
	return getTextDoc(tabWidget_->currentWidget());
}

int DocHandler::currentIndex() const {
	return tabWidget_->currentIndex();
}

void DocHandler::setTabText(int index, const QString& text) {
	tabWidget_->setTabText(index, text);
}

int DocHandler::docCount() const {
	return tabWidget_->count();
}


///////////////////////////////////////////////////////////////////////
//	Document related methods
///////////////////////////////////////////////////////////////////////

bool DocHandler::confirmDocClose() {
	Document* doc = currentDoc();
	if (doc->isNull())
		return false;

	bool close = true;
	if (doc->isModified()) {
		QString str = tr("The document ") + doc->fileName();
		str += tr(" has been modified.\nDo you want to save your changes?");
		int ret = QMessageBox::warning(0, tr("Close document"),
					str, QMessageBox::Save | QMessageBox::Discard
					| QMessageBox::Cancel, QMessageBox::Save);
		switch (ret) {
		case QMessageBox::Save:
			saveDoc();
			close = true;
			break;
		case QMessageBox::Discard:
			close = true;
			break;
		case QMessageBox::Cancel:
			close = false;
			break;
		}
	}
	return close;
}

void DocHandler::addTab(Document* doc) {
	QString fileName = doc->fileName();
	QWidget* w = doc->widget();
	tabWidget_->addTab(w, (fileName.isEmpty() ? tr("Noname") : fileName.section('/', -1, -1)));
	w->setFocus();
	tabWidget_->setCurrentWidget(w);
	if (docCount() == 1)
		changeCurDoc(0);
}

void DocHandler::newDoc() {
	Document* doc = createDoc("");
	storage_->addDoc(getTextDoc(doc));
	addTab(doc);
}

void DocHandler::openDoc(const QString& name) {
	QString fileName;
	if (name.isEmpty()) {
		fileName = QFileDialog::getOpenFileName(mw_,
			tr("Open file"), MainSettings::lastOpenDir(),
			"All files (*);;"
			"C++ files (*.cpp *.cxx *.CPP *.CXX);;"
			"Header files (*.h *.hpp *.H *.HPP)"
			);
	}
	else {
		fileName = QFileInfo(name).absoluteFilePath();
	}

	if (! fileName.isEmpty()) {
		int index = storage_->findIndex(fileName);
		if (index < 0) {
			Document* doc = createDoc(fileName);
			storage_->addDoc(getTextDoc(doc));
			addTab(doc);
		}
		else {
			tabWidget_->setCurrentIndex(index);
		}
		MainSettings::setLastOpenDir(QFileInfo(fileName).absolutePath());
	}
}

void DocHandler::saveDoc() {
	Document* doc = currentDoc();
	if (doc->isNull())
		return;

	if (doc->fileName().isEmpty())
		saveDocAs();
	else
		doc->save();
}

void DocHandler::saveDocAs() {
	Document* doc = currentDoc();
	if (doc->isNull())
		return;

	QString fName = QFileDialog::getSaveFileName(mw_, tr("Save as"), MainSettings::lastSaveDir());
	if (!fName.isEmpty()) {
		doc->saveAs(fName);
		setTabText(currentIndex(), fName.section('/', -1, -1));			//1
	}
}

bool DocHandler::closeDoc() {
	int index = currentIndex();
	bool res = false;
	TextDoc* doc = storage_->getDoc(index);
	if (!doc->isNull()) {
		if (confirmDocClose()) {
			storage_->removeDoc(index);
			res = true;
		}
	}
	changeCurDoc(currentIndex());
	return res;
}

bool DocHandler::closeAllDocs() { 
	bool res = true;
	while (docCount() > 0) {
		if (!closeDoc()) {
			res = false;
			break;
		}
	}
	return res;
}

bool DocHandler::changeCharset(const QString& charset) {
	bool res = false;
	TextDoc* textDoc = getTextDoc(currentDoc());
	if (textDoc->isNull())
		return res;

	bool ok = true;
	if (textDoc->isModified() && !confirmDocClose())
		ok = false;
	if (ok) {
		textDoc->setCharset(charset);
		res = true;
		emit statusTextChanged(2, textDoc->charset());
	}
	return res;
}

void DocHandler::docModified(bool mod) {
	Document* doc = currentDoc();
	if (doc->isNull())
		return;

	int index = currentIndex();
	QString label(doc->fileName().section('/', -1, -1));		//1
	if (label.isEmpty())
		label = tr("Noname");
	if (mod)
		label += "*";
	setTabText(index, label);
}

void DocHandler::changeCurDoc(int index) {
	TextDoc* doc = storage_->getDoc(index);
	if (!doc->isNull()) {
		doc->widget()->setFocus();
		QString fileName = doc->fileName();
		mw_->setWindowTitle("JuffEd - " + (fileName.isEmpty() ? tr("Noname") : fileName));
		emit statusTextChanged(1, fileName);
		emit statusTextChanged(2, doc->charset());
	}
	else {
		mw_->setWindowTitle("JuffEd");
		emit statusTextChanged(0, "");
		emit statusTextChanged(1, "");
		emit statusTextChanged(2, "");
	}
	showCursorPos();
}

void DocHandler::showCursorPos() {
	int row = 0, col = 0;
	TextDoc* tDoc = getTextDoc(currentDoc());
	if (!tDoc->isNull()) {
		tDoc->getCursorPos(row, col);
		emit statusTextChanged(0, tr(" Row: %1, Col: %2 ").arg(row).arg(col));
	}
}

void DocHandler::find(const QString& str, bool matchCase, bool backwards) {
	TextDoc* textDoc = getTextDoc(currentDoc());
	if (textDoc->isNull())
		return;

	if (str.isEmpty()) {
		FindDlg dlg(textDoc->widget());
		if (dlg.exec() == QDialog::Accepted) {
			QString text = dlg.text();
			bool matchCase = dlg.matchCase();
			bool backward = dlg.backward();
			textDoc->find(text, matchCase, backward);
		}
	}
	else {
		textDoc->find(str, matchCase, backwards);
	}
}

///////////////////////////////////////////////////////////////////////

void DocHandler::processTheCommand() {
	QAction* a = qobject_cast<QAction*>(sender());
	if (a != 0) {
		TextDoc* textDoc = getTextDoc(currentDoc());
		if (textDoc->isNull())
			return;

		CommandID id = CommandID(a->data().toInt());
		switch (id) {

		case ID_EDIT_COPY:
			textDoc->copy();
			break;

		case ID_EDIT_CUT:
			textDoc->cut();
			break;

		case ID_EDIT_PASTE:
			textDoc->paste();
			break;

		case ID_EDIT_UNDO:
			textDoc->undo();
			break;

		case ID_EDIT_REDO:
			textDoc->redo();
			break;

		case ID_DOC_NEXT:
			tabWidget_->setCurrentIndex((currentIndex() + 1) % docCount());
			break;

		case ID_DOC_PREV:
			tabWidget_->setCurrentIndex((currentIndex() - 1 + docCount()) % docCount());
			break;

		case ID_FIND:
			find("", true, true);
			break;

		case ID_FIND_NEXT:
			find(FindDlg::lastText(), FindDlg::lastMatchCase(), false);
			break;

		case ID_FIND_PREV:
			find(FindDlg::lastText(), FindDlg::lastMatchCase(), true);
			break;

		case ID_GOTO_LINE: {
			bool ok = false;
			int line = QInputDialog::getInteger(0, tr("Go to line"), tr("Go to line"), 1, 1, textDoc->lineCount(), 1, &ok);
			if (ok)
				textDoc->gotoLine(line);
			break;
		}

		default:
			qDebug(qPrintable(QString::number(id)));
			break;
		}
	}
}
